package com.gitee.wsl.data.observer

import com.gitee.wsl.ext.base.currentTime
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.update
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlin.coroutines.CoroutineContext
import kotlin.random.Random
import kotlin.time.ExperimentalTime


internal typealias KmpKvStoreObserver = suspend (DataChangeEvent<String, Any?>) -> Unit

internal object KmpKvStoreKeyGenerator {

    @OptIn(ExperimentalTime::class)
    private val counter = atomic(Random(currentTime.toEpochMilliseconds()).nextLong())

    @OptIn(ExperimentalStdlibApi::class, ExperimentalTime::class)
    fun createUniqueKey(): String =
        currentTime.epochSeconds.toHexString().takeLast(10) + counter.incrementAndGet().toHexString()
}

object KmpKvStoreObserverRegistry {
    private data class ValueListenerContext(
        val coroutineContext: CoroutineContext = Dispatchers.Default.limitedParallelism(1),
        val listeners: List<KmpKvStoreObserver> = emptyList(),
    )

    private val scope = CoroutineScope(Dispatchers.Default)
    private val listeners: AtomicRef<Map<String, Map<String, ValueListenerContext>>> = atomic(emptyMap())

    fun addListener(node: String, key: String, listener: KmpKvStoreObserver) = listeners.update { it ->
        it.toMutableMap().apply {
            this[node] = (this[node] ?: emptyMap()).toMutableMap().apply {
                val context = (this[key] ?: ValueListenerContext()).let { it.copy(listeners = it.listeners + listener) }
                this[key] = context
            }
        }
    }

    fun removeListener(node: String, key: String, listener: KmpKvStoreObserver) = listeners.update { it ->
        it.toMutableMap().apply {
            this[node] = (this[node] ?: emptyMap()).toMutableMap().apply {
                val listeners = (this[key] ?: ValueListenerContext())
                    .let { it.copy(listeners = it.listeners - listener) }
                this[key] = listeners
            }
        }
    }

    fun notifyValueChange(node: String, key: String, value: Any?,newValue:Any?) {
        val context = listeners.value[node]?.get(key) ?: return
        scope.launch(context.coroutineContext) {
            context.listeners.forEach { it(DataChangeEvent.Updated(key,value,newValue)) }
        }
    }

    fun notifyValueRemove(node: String, key: String) {
        val context = listeners.value[node]?.get(key) ?: return
        scope.launch(context.coroutineContext) {
            context.listeners.forEach { it(DataChangeEvent.Removed(key,null)) }
        }
    }

    fun notifyClear(node: String) {
        scope.launch {
            listeners.value[node]?.forEach { keyMapEntry ->
                val context = keyMapEntry.value
                scope.launch(context.coroutineContext) {
                    context.listeners.forEach { it(DataChangeEvent.Removed(keyMapEntry.key,null)) }
                }
            }
        }
    }

    inline fun <reified V, R> observe(
        nodeName: String,
        key: String,
        crossinline mapper: (rawValue: DataChangeEvent<String, V?>) -> DataChangeEvent<String, R?>?,
    ): Flow<DataChangeEvent<String,R?>> = channelFlow {
        try {
            val listener: KmpKvStoreObserver = listener@{ value: DataChangeEvent<String, Any?> ->
//              if (value !is DataChangeEvent<String, V?>) return@listener
//                if (value == null) {
//                    send(null)
//                    return@listener
//                }
                val mappedValue = mapper(value as DataChangeEvent<String, V?>)?: return@listener
                send(mappedValue)
            }
            addListener(nodeName, key, listener)
            awaitClose { removeListener(nodeName, key, listener) }
        } catch (_: Exception) {
            // NoOp
        }
    }.distinctUntilChanged()

}
